package cn.ronghuanet.car.service.impl;

import cn.ronghuanet.base.config.HighlightResultMapper;
import cn.ronghuanet.base.util.PageInfo;
import cn.ronghuanet.base.util.SearchUtil;
import cn.ronghuanet.car.doc.CarDoc;
import cn.ronghuanet.car.query.CarSearchQuery;
import cn.ronghuanet.car.repository.CarDocRepository;
import cn.ronghuanet.car.service.ICarSearchService;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
public class CarSearchServiceImpl implements ICarSearchService {

    @Autowired
    private CarDocRepository carDocRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private HighlightResultMapper resultMapper;

    @Override
    public PageInfo<CarDoc> search(CarSearchQuery query) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 高级查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); // 拿到dsl查询的bool这一层级
        List<QueryBuilder> must = boolQuery.must();
        List<QueryBuilder> filter = boolQuery.filter();

        // 关键字查询 - keyword -> 分词查询(title typename shopname carinfo)
        if(StringUtils.isNotBlank(query.getKeyword())){
            must.add(QueryBuilders.multiMatchQuery(query.getKeyword(),"title","typeName","shopName","carInfo"));
        }

        // 类型查询 typeId
        if(Objects.nonNull(query.getTypeId())){
            filter.add(QueryBuilders.termQuery("typeId",query.getTypeId()));
        }

        // 价格  minPrice maxPirce    1-3 3-5     maxPrice>costprice>=minPrice
        if(Objects.nonNull(query.getMinPrice())){
            filter.add(QueryBuilders.rangeQuery("costprice").gte(query.getMinPrice()));
        }
        if(Objects.nonNull(query.getMaxPrice())){
            filter.add(QueryBuilders.rangeQuery("costprice").lt(query.getMaxPrice()));
        }

        // 车龄  reigstertime
        // 5年以上  5年以下  5年 2023/8/23 - 5年 = 2018/8/23
        // 先计算出carAge所对应的时间点  当前时间-carAge
        // 以上  reigstertime<2018/8/23  以内  reigstertime>2018/8/23
        if(Objects.nonNull(query.getCarAge())){
            // 根据车龄计算要比较的时间节点
            long time = DateUtils.addYears(new Date(), 0 - query.getCarAge()).getTime();
            if(query.getCarAgeType() == 1){ // 以上
                filter.add(QueryBuilders.rangeQuery("reigstertime").lt(time));
            }else{
                // 以内,就是说比时间节点要大的时间
                filter.add(QueryBuilders.rangeQuery("reigstertime").gte(time));
            }
        }

        // 经纬度距离查询
        /*if(Objects.nonNull(query.getLat()) && Objects.nonNull(query.getLon()) && Objects.nonNull(query.getDistance())){
            GeoDistanceQueryBuilder shopPoint = QueryBuilders.geoDistanceQuery("shopPoint");
            // 设置要进行查询地点的经纬度设置
            shopPoint.point(query.getLat(),query.getLon());
            // 设置要查询的范围
            shopPoint.distance(query.getDistance(), DistanceUnit.KILOMETERS);
            filter.add(shopPoint);
        }*/

        // 其他字段
        if(Objects.nonNull(query.getCosteffective())){
            filter.add(QueryBuilders.termQuery("costeffective",query.getCosteffective()));
        }
        if(Objects.nonNull(query.getRushsale())){
            filter.add(QueryBuilders.termQuery("rushsale",query.getRushsale()));
        }
        if(Objects.nonNull(query.getQuasinewcar())){
            filter.add(QueryBuilders.termQuery("quasinewcar",query.getQuasinewcar()));
        }
        if(Objects.nonNull(query.getTransitivecountry())){
            filter.add(QueryBuilders.termQuery("transitivecountry",query.getTransitivecountry()));
        }

        queryBuilder.withQuery(boolQuery);

        // 排序查询
        SortOrder sortOrder = SortOrder.ASC;    // 默认按照 asc正序排序
        if("desc".equals(query.getSortType())){
            sortOrder = SortOrder.DESC;
        }
        // 如果前端没有传递排序字段,应该按照默认字段来排序
        if(StringUtils.isNotBlank(query.getSortField())){
            SortBuilder sortBuilder = SortBuilders.fieldSort(query.getSortField()).order(sortOrder);
            queryBuilder.withSort(sortBuilder);
        }else{
            // 在项目中默认字段应该由产品经理指定,我们这默认字段按照距离排序
            // 距离排序字段,字段必须是point类型的才可以
            // 在项目中,应该由前端传递用户所在位置的经纬度,但是我们前端没有,所以现在后端把经纬度写死
            GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("shopPoint", query.getLat(), query.getLon());
            sortBuilder.order(sortOrder);
            queryBuilder.withSort(sortBuilder);
        }

        // 高亮查询
        HighlightBuilder.Field field1 = new HighlightBuilder.Field("title").preTags("<span style='color:red'>").postTags("</span>");
        HighlightBuilder.Field field2 = new HighlightBuilder.Field("typeName").preTags("<span style='color:red'>").postTags("</span>");
        HighlightBuilder.Field field3 = new HighlightBuilder.Field("shopName").preTags("<span style='color:red'>").postTags("</span>");
        HighlightBuilder.Field field4 = new HighlightBuilder.Field("carInfo").preTags("<span style='color:red'>").postTags("</span>");

        queryBuilder.withHighlightFields(field1,field2,field3,field4);

        // 聚合查询
        TermsAggregationBuilder typeIdGroup = AggregationBuilders.terms("typeIdGroup").field("typeId").order(BucketOrder.count(true))
                .subAggregation(
                        AggregationBuilders.terms("typeNameGroup").field("typeName.keyword").order(BucketOrder.count(true))
                );
        TermsAggregationBuilder shopIdGroup = AggregationBuilders.terms("shopIdGroup").field("shopId").order(BucketOrder.count(true))
                .subAggregation(
                        AggregationBuilders.terms("shopNameGroup").field("shopName.keyword").order(BucketOrder.count(true))
                );
        queryBuilder.addAggregation(typeIdGroup);
        queryBuilder.addAggregation(shopIdGroup);

        // 分页查询
        // PageRequest.of  第一个参数,是你当前要查询的页数-1
        queryBuilder.withPageable(PageRequest.of(query.getCurrentPage()-1,query.getPageSize()));

        //Page<CarDoc> docPage = carDocRepository.search(queryBuilder.build());
        // 要做高亮查询,就需要使用高亮结果映射器,高亮结果映射器是我们自己写的,所以在查询时需要重新指定高亮结果映射器,
        // carDocRepository接口中是没有指定自定义结果映射器方法,所以需要使用ElasticsearchTemplate来做查询
        AggregatedPage<CarDoc> docPage = elasticsearchTemplate.queryForPage(queryBuilder.build(), CarDoc.class, resultMapper);

        // 将docPage中的聚合结果转为map,然后将map给PageInfo返回给前端就可以了
        Aggregations aggregations = docPage.getAggregations();
        Map<String, Object> map = SearchUtil.handleTermsAggsData(aggregations);

        // 将es的分页对象转为我们自己的分页对象,返回给前端
        long total = docPage.getTotalElements();
        List<CarDoc> rows = docPage.getContent();
        // 将返回的数据做参数转换,时间&地址
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
        for (CarDoc row : rows) {
            row.setReigstertimeStr(sdf.format(row.getReigstertime()));
            String shopAddress = row.getShopAddress();
            shopAddress = shopAddress.split("区")[0];
            row.setShopAddress(shopAddress+"区");
        }
        return new PageInfo<CarDoc>(total,rows,map);
    }
}
